# coding: utf-8

from .expression import Expression, ExpressionType


class Query:
    """Query base class
    """

    def __init__(self, model=None, db=None, condition_str_tuple=tuple(), condition_dict={}):
        if not all([model, db]):
            raise ValueError("model or db required")

        self.model = model
        self.db = model.__database__

        if db:
            self.db = db

        self.expression = Expression(
            table=self.model.__table__,
            condition_str_tuple=condition_str_tuple,
            condition_dict=condition_dict
        )
        self.expression_type = ExpressionType.query
        self._init_cache()

    @property
    def _fields(self):
        ret = self.db.execute('DESC `%s`' % self.model.__table__, select=True)
        if not ret:
            raise ValueError("Database fields get Error")
        ret_data = self.db.cursor.fetchall()
        _fields_name_list = [r[0] for r in ret_data]
        self._fields_name = _fields_name_list
        return self._fields_name

    def render_sql(self):
        return self.expression.render_sql(self.expression_type)

    def render_values(self):
        return self.expression.values

    def _clear_cache(self):
        """clear model cache when update sql expression"""
        self._yield_data = None
        self._count = None

    def _init_cache(self):
        """init model cache when init Model instance"""
        self._yield_data = None
        self._count = 0
        self._fields

    def where(self, *condition_str_tuple, **condition_dict):
        """where condition

        >>> Query.where(id=1) ==> WHERE id=1
        >>> Query.where('id > 10', 'id < 20', name='ada') ==> WHERE name='ada' AND 'id > 10' AND 'id < 20'
        >>> Query.where(name='ada').where(status='normal').where('id < 10') => WHERE name='ada' AND status='normal' AND id < 10

        """
        self._clear_cache()
        if condition_str_tuple:
            self.expression.update_condition_str_tuple(condition_str_tuple)
        if condition_dict:
            self.expression.update_condition_dict(condition_dict)

        return self

    def update(self, new_record=False, **kw):
        """update Database records
        :param new_record: set True to insert a new record
        :param kw: the record's values, such as name='foo' means `name`='foo'
        """
        self._clear_cache()
        self.expression_type = ExpressionType.insert if new_record else ExpressionType.update
        if kw:
            self.expression.update_fields_dict(kw)

        try:
            _sql = self.render_sql()
            _values = self.render_values()

            if _values:
                ret = self.db.execute(_sql, _values)
            else:
                ret = self.db.execute(_sql)
        except:
            raise

        self.expression_type = ExpressionType.query
        return ret

    def delete(self):
        """delete record"""
        self._clear_cache()
        self.expression_type = ExpressionType.delete
        try:
            _sql, _values = self.render_sql(), self.render_values()
            return self.db.execute(_sql, _values)
        except:
            raise
        self.expression_type = ExpressionType.query
        return self.update()

    def raw_sql(self, sql: str, values: tuple=tuple()):
        try:
            if values:
                ret = self.db.execute(sql, values)
            else:
                ret = self.db.execute(sql)
        except:
            raise
        return ret

    def yield_all(self, return_model_instance=True):
        for ret in self.db.yield_data():
            yield self.return_model_instance(ret) if return_model_instance else ret

    def yield_data(self, return_model_instance=True):
        if self._yield_data:
            return self._yield_data

        try:
            _sql = self.render_sql()
            _values = self.render_values()
            if _values:
                self.db.execute(_sql, _values, select=self.expression_type == ExpressionType.query)
            else:
                self.db.execute(_sql, select=self.expression_type == ExpressionType.query)
        except:
            raise

        for ret in self.db.yield_data():
            yield self.return_model_instance(ret) if return_model_instance else ret

    def return_model_instance(self, db_ret: tuple):
        model = self.model
        fields = self._fields_name
        if len(fields) != len(db_ret):
            raise ValueError("ret trans model Error")

        attrs = {}
        for field, value in zip(fields, db_ret):
            attrs[field] = value
        return model(**attrs, new_record=False, condition_str_tuple=self.expression.condition_str_tuple,
                     condition_kw=self.expression.condition_dict)

    def first(self):
        ret = None
        for _data in self.yield_data():
            ret = _data
            break
        return ret

    def count(self):
        if self.expression_type != ExpressionType.query:
            raise RuntimeError("only query result can return count")
        _sql = self.render_sql()
        _values = self.render_values()
        if "SELECT * FROM" in _sql:
            _sql = _sql.replace("SELECT * FROM", "SELECT COUNT(*) FROM")
        self.raw_sql(_sql, _values)
        ret, = self.db.cursor.fetchall()
        return ret[0]

    def distinct_by(self, field: str):
        self.expression.distinct_by(field)
        _sql = self.render_sql()
        _values = self.render_values()
        self.raw_sql(_sql, _values)
        return self.db.yield_data()

    def order_by(self, field: str):
        self.expression.order_by(field)
        return self

    def group_by(self, field: str):
        """simple support of GROUP BY"""
        self.expression.group_by(field)
        _sql = self.render_sql()
        _values = self.render_values()
        self.raw_sql(_sql, _values)
        return self.db.yield_data()

    def value_in(self, field: str, values: tuple):
        """implement of `IN`"""
        self.expression.value_in(field, values)
        return self

    def limits(self, limit: int, offset: int=0):
        self.expression.limits(limit, offset)
        return self

    def __str__(self):
        ret = list(self.yield_data())
        self._yield_data = ret
        return str(ret)

    __repr__ = __str__

    def __iter__(self):
        return self.yield_data()

    def __getitem__(self, index):
        return list(self.yield_data())[index]

    def __len__(self):
        return len(list(self.yield_data()))
